
import { getError, gfx, ImageAsset, Texture2D } from "cc";
import { XIAOMI } from "cc/env";

/**
 * @en The image source, can be HTML canvas, image type or image in memory data
 * @zh 图像资源的原始图像源。可以来源于 HTML 元素也可以来源于内存。
 */
type ImageSource = HTMLCanvasElement | HTMLImageElement | ImageBitmap; // 引擎解析到内存中可用的图像资源类型

/**
 * @en The decryption task
 * @zh 解密任务
 */
type DecryptionTask = {
	img: HTMLImageElement;
	path: string;
	name: string;
	success: boolean;
	done: boolean;
	try_count: number;
	img_data: ImageData;
	asset: ImageAsset;
	complete: (source: ImageSource) => void;
}

const ccwindow: typeof window = typeof globalThis.jsb !== 'undefined' ? (typeof jsb.window !== 'undefined' ? jsb.window : globalThis) : globalThis;
/** 解密序列，要与加密脚本中的设置保持一致 */
const decryption_order = '2784506931'.split('').map(Number);

/** 解密任务列表 */
const task_list: Array<DecryptionTask> = [];
/** 资源解析计数 */
var parse_count = 0;
/** 资源读取失败时的尝试上限 */
const try_count_limit = 200;
/** 资源解析完成数 */
var complete_count = 0;

/**
 * @en Parse the image file
 * @zh 解析图片文件
 * @param file 源文件
 * @param options
 * @param onComplete 解析成功的回调
 */
export function parseImage(file: string|HTMLImageElement, options: any, onComplete: (err: any, image: ImageSource) => void) {
	parse_count++;

	if(file){
		let file_type = typeof file == 'string' ? 'string' : Reflect.getPrototypeOf(file).constructor.name;
		console.log('parseImage count:', parse_count.toString().padStart(3, '0'), file_type, (typeof file == 'string' ? file : file.src).split('/').pop());
		if(typeof file === 'string'){
			let load_start = Date.now();
			loadImage(file, (err, img)=>{
				let list = file.split('/');
				logInfo('LoadImage:'+(err?'error':'success'), parse_count, list.pop(), list.join('/'), load_start, Date.now());
				if(err){
					onComplete(err, null);
					console.error('loadImage', err);
				}else{
					decryptImage(img, source=>onComplete(null, source));
					//onComplete(null, img);
				}
			});
		}else{
			decryptImage(file, source=>onComplete(null, source));
		}
	}else{
		// @ts-ignore
		onComplete(null, file);
	}
}

/**
 * 加载图片
 * @param url HTMLImageElement.src
 */
function loadImage(url: string, loaddone: (err: any, image: HTMLImageElement)=>void){
	let img = new ccwindow.Image();

    if (ccwindow.location.protocol !== 'file:' || XIAOMI) {
        img.crossOrigin = 'anonymous';
    }

	img.addEventListener('load', ()=>loaddone(null, img), {once: true});
	img.addEventListener('error', ()=>loaddone(new Error(getError(4930, url)), null), {once: true});

	img.src = url;
}

function decryptImage(img: HTMLImageElement, complete: (source: ImageSource) => void){
	let list = img.src.split('/');
	let name = list.pop();
	let path = list.join('/');
	runDecryptTask({
		path, name, img, complete,
		img_data: createImageData(img.width, img.height),
		asset: new ImageAsset(img),
		try_count: 0,
		success: false,
		done: false
	});
}

let ing_task = false;
let idx_task = 0;
function runDecryptTask(task?: DecryptionTask){
	if(task){
		//task_list.push(task);
		task_list.splice(complete_count, 0, task);
	}

	if(ing_task){
		return void 0;
	}

	ing_task = true;
	idx_task = complete_count;

	let start: number;
	let end: number;
	let get_start: number;
	let get_end: number;

	while(idx_task < task_list.length){
		let task = task_list[idx_task];

		if(task.success || task.try_count >= try_count_limit){
			idx_task++;
			continue;
		}

		task.try_count++;

		get_start = Date.now();
		getImageData(task.asset, task.img_data);
		get_end = Date.now();

		task.success = task.img_data.data.filter(_=>_>0).length > 0;

		if(task.success){
			task.done = true;
			start = Date.now();
			decryptionBuffer(task.img_data.data, Math.max(1, getUnderMaxPrime(Math.min(task.img.width, task.img.height))));
			end = Date.now();
			logInfo('Decryption', idx_task, task.name, task.path, start, end);

			start = Date.now();
			task.complete(createCanvasSource(task.img_data));
			end = Date.now();
			logInfo('CreateSource', idx_task, task.name, task.path, start, end);
		}

		if(task.done){
			logInfo('log-img-data', idx_task, task.name, task.img_data.data.length+':valid-length:'+task.img_data.data.filter(_=>_>0).length, get_start, get_end);
			logInfo('GetImageData:'+task.try_count, idx_task, task.name, task.path, get_start, get_end);
			if(idx_task!=complete_count){
				task_list.splice(complete_count, 0, task_list.splice(idx_task, 1)[0]);
			}
			complete_count++;
		}

		idx_task++;
	}

	ing_task = false;

	if(task_list.length>=parse_count && task_list.length>complete_count){
		idx_task = complete_count;

		while(complete_count < parse_count){
			let task = task_list[idx_task];
			task.try_count++;

			getImageData(task.asset, task.img_data);

			task.success = task.img_data.data.filter(_=>_>0).length > 0;

			if(task.success){
				task.done = true;
				decryptionBuffer(task.img_data.data, Math.max(1, getUnderMaxPrime(Math.min(task.img.width, task.img.height))));
				task.complete(createCanvasSource(task.img_data));
			}else if(task.try_count >= 1000){
				task.done = true;
				task.complete(null);
			}

			if(task.done){
				if(idx_task!=complete_count){
					task_list.splice(complete_count, 0, task_list.splice(idx_task, 1)[0]);
				}
				complete_count++;
				console.log(task.name, 'try-count:', task.try_count, 'size:', task.img_data.data.length);
			}

			idx_task++;

			if(idx_task >= task_list.length){
				idx_task = complete_count;
			}
		}
	}
}

/**
 * 创建Canvas图像源
 * @param img_data
 */
function createCanvasSource(img_data: ImageData){
	let canvas = ccwindow.document.createElement('canvas');
	canvas.width = img_data.width;
	canvas.height = img_data.height;
	canvas.getContext('2d').putImageData(img_data, 0, 0);

	return canvas;
}

const get_data_cache = {
	texture: new Texture2D(),
	region: new gfx.BufferTextureCopy(),
};
function getImageData(asset: ImageAsset, output: ImageData){
	get_data_cache.texture.image = asset;
	get_data_cache.texture.updateImage();

	// 创建一个区域描述对象
	get_data_cache.region.texOffset.x = 0;
	get_data_cache.region.texOffset.y = 0;
	get_data_cache.region.texExtent.width = asset.width;
	get_data_cache.region.texExtent.height = asset.height;

	// 将纹理拷贝到缓冲区
	gfx.deviceManager.gfxDevice.copyTextureToBuffers(get_data_cache.texture.getGFXTexture(), [output.data], [get_data_cache.region]);

	return output;
}

const decrypt_cache = {
	buffer: new Uint8ClampedArray(0),
	cut_count: 0,
	range: 0,
	idx_map: [[0]],
	offset: 0
};
function decryptionBuffer(buffer: Uint8ClampedArray, cut_length: number){
	if(cut_length >= buffer.length){
		return buffer;
	}

	if(!(decrypt_cache.buffer.length >= buffer.length)){
		decrypt_cache.buffer = new Uint8ClampedArray(buffer.length);
	}

	decrypt_cache.cut_count = Math.floor(buffer.length / cut_length);
	decrypt_cache.offset = 0;

	do{
		decrypt_cache.idx_map = createDecryptionIdxMap(decrypt_cache.cut_count-decrypt_cache.offset);
		decrypt_cache.range = decrypt_cache.idx_map[0].length * Math.pow(10, decrypt_cache.idx_map.length-1);

		for(let i=0, idx=0, trans_idx=0; i < decrypt_cache.range; i++){
			idx = (decrypt_cache.offset + i) * cut_length;
			trans_idx = (decrypt_cache.offset + transformIndex(i, decrypt_cache.idx_map)) * cut_length;
			decrypt_cache.buffer.set(buffer.slice(idx, idx+cut_length), trans_idx);
		}

		decrypt_cache.offset += decrypt_cache.range;
	}while(decrypt_cache.offset < decrypt_cache.cut_count);

	buffer.set(decrypt_cache.buffer.slice(0, decrypt_cache.cut_count * cut_length));

	return buffer;
}

/**
 * 创建解密索引映射
 * @param length
 */
function createDecryptionIdxMap(length: number){
	return length.toString().split('').map((el, idx)=>{
		let result: number[] = [];

		(new Array(idx==0 ? parseInt(el) : decryption_order.length))
		.fill(0).map((_, i)=>i)
		.sort((a, b)=>decryption_order.indexOf(a) - decryption_order.indexOf(b))
		.forEach((e, i)=>result[e] = i);

		return result;
	});
}

/**
 * 转换索引
 * @param index
 * @param trans_map
 */
function transformIndex(index: number, trans_map: number[][]){
	let list = index.toString().padStart(trans_map.length, '0').split('').map(el=>parseInt(el));
	let str = list.map((el, i)=>trans_map[i][el]).join('');
	return parseInt(str);
}

const ctx_2d_create_image_data = ccwindow.document.createElement('canvas').getContext('2d');
function createImageData(img_data: ImageData): ImageData;
function createImageData(width: number, height: number): ImageData;
function createImageData(width: number|ImageData, height?: number){
	if(typeof width === 'number'){
		return ctx_2d_create_image_data.createImageData(width, height);
	}else{
		return ctx_2d_create_image_data.createImageData(width);
	}
};

/**
 * 获取小于指定值的最大素数
 * @param num
 */
function getUnderMaxPrime(num: number){
	let value = num;
	let prime = Math.min(value, findMaxPrime(value));
	while(prime >= num){
		value = value-1;
		prime = Math.min(value, findMaxPrime(value));
	}

	return prime;
}

/**
 * 查找最大素数
 * @param num
 */
function findMaxPrime(num: number){
	let max_prime = num;

	if(max_prime > 3){
		for(let i=max_prime; i>2; i--){
			let is_prime = true;
			for(let j=2; j*j<=i; j++){
				if(i % j == 0){
					is_prime = false;
					break;
				}
			}
			if(is_prime){
				max_prime = i;
				break;
			}
		}
	}else{
		max_prime = Math.max(2, max_prime);
	}

	return max_prime;
}

function logInfo(tag: string, index: number, name: string, path: string, start: number, end: number){
	let time = end - start;
	tag = '#' + index.toString().padStart(3, '0') + '@ ' + tag.padEnd(18, ' ') + '>>';
	if(time>=0){
		//tag += '|' + formatTime(time) + '|';
		tag += (time==0 ? '[!time!]' : '[ time ]').replace('time', formatTime(time));
	}
	console.log(tag, name, path, 'start:', start, 'end:', end);
}

function formatTime(time: number){
	let s = Math.floor(time / 1000);
	let m = Math.floor(s / 60);
	let h = Math.floor(m / 60);

	return [h, m, s].map(v=>v.toString().padStart(2, '0')).join(':') + ' ' + (time % 1000).toString().padStart(3, '0') + 'ms';
}
